Terminology
Transcoding
Audio & Video tags
MSE
DASH
Lossless compression allows us to more efficiently represent the same uncompressed data and also sounds fantastic on your expensive audio equipment.
Lossy compression sacrifices fidelity for size and allows you to store thousands of songs on your phone.
Lossless to lossy is a one way conversion
You cannot get back fidelity once it's been thrown away
Your free and Open Source transcoding swiss army knife.
$ ffmpeg -h full 2>/dev/null| wc -l
5424
ffmpeg -i input.wav output.mp3
ffmpeg -i input.y4m -i input.wav output.webm
ffmpeg -codecs
will tell you if you can decode from one codec and encode into another.
$ ffmpeg -codecs
DEV.LS h264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (decoders: h264 h264_vda ) (encoders: libx264 libx264rgb )
DEV.L. vp8 On2 VP8 (decoders: vp8 libvpx ) (encoders: libvpx )
DEV.L. vp9 Google VP9 (decoders: vp9 libvpx-vp9 ) (encoders: libvpx-vp9 )
DEA.L. mp3 MP3 (MPEG audio layer 3) (decoders: mp3 mp3float ) (encoders: libmp3lame )
DEA.L. opus Opus (Opus Interactive Audio Codec) (decoders: opus libopus ) (encoders: libopus )
If you're missing an expected encoder/decoder, you probably need to install a shared library and rebuild/reinstall ffmpeg. Not fun.
ffmpeg -i input.file
will tell you a lot about a file.
$ ffmpeg -i bunny.mp4
Duration: 00:01:00.10 ...
Stream #0:0(eng): Audio: aac ... 22050 Hz, stereo, ... 65 kb/s
Stream #0:1(eng): Video: h264 ... 640x360, 612 kb/s, 23.96 fps ...
aka muxing
$ ffmpeg -i bunny.mp4
Stream #0:0(eng): Audio: aac ... 22050 Hz, stereo, ... 65 kb/s
Stream #0:1(eng): Video: h264 ... 640x360, 612 kb/s, 23.96 fps ...
Stream #0:2(eng): Data: none (rtp / 0x20707472), 45 kb/s
Stream #0:3(eng): Data: none (rtp / 0x20707472), 5 kb/s
$ ffmpeg -i bunny.mp4 -map 0:0 -map 0:1 -c copy bunny_clean.mp4
$ ffmpeg -i bunny_clean.mp4
Stream #0:0(eng): Audio: aac ... 22050 Hz, stereo, ... 65 kb/s
Stream #0:1(eng): Video: h264 ... 640x360, 612 kb/s, 23.96 fps ...
A browser will make Byte Range Requests on behalf of a media element to buffer content.
Request Headers:
Response Headers:
Why?
Video is easily an order of magnitude larger in file size than audio.
Waste of bandwidth if user downloads entire file, but watches only a part.
var xhr = new XMLHttpRequest;
xhr.open('GET', 'song.mp3');
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
var audio = new Audio;
audio.src = URL.createObjectURL(new Blob([xhr.response], {
type: 'audio/mpeg' }));
audio.oncanplaythrough = audio.play.bind(audio);
};
xhr.send();
plus byte range requests?
Audio tags don't let us combine arraybuffers, they require one contiguous arraybuffer. Web Audio API can help here.
But...
Results in audible clicks between segments. Finding the proper range to segment by is very difficult. Web Audio API does not help here.
Browser | video/mp4 | video/webm | video/mp2t | audio/mpeg | audio/aac |
---|---|---|---|---|---|
Chrome45 | ✓ | ✓* | ✗ | ✓ | ✓ |
Firefox 38 | ✓** | ✗ | ✗ | ✗ | ✗ |
IE 11 | ✓ | ✗ | ✓ | ✗ | ✓ |
Opera 30 | ✓* | ✓* | ✗ | ✗ | ✗ |
Safari 8 | ✓* | ✗ | ✗ | ✗ | ✗ |
* Needs codec specified MediaSource.isTypeSupported('mime; codecs=""'); List ** whitelisted to very few sites like YouTube, FF 38-41/42? Test in Nightly, media.mediasource.whitelist -> false in about:config
mp4 needs to be "fragmented." From ISO BMFF Byte Stream Format §3:
An ISO BMFF initialization segment is defined in this specification as a single File Type Box (ftyp) followed by a single Movie Header Box (moov).
wut?
I highly recommend axiomatic-systems/Bento4 on GitHub.
$ ./mp4dump ~/Movies/devtools.mp4 | head [ftyp] size=8+24 ... [free] size=8+0 [mdat] size=8+85038690 [moov] size=8+599967 ...
...ftype followed by a single moov...
$ ./mp4fragment ~/Movies/devtools.mp4 devtools_fragmented.mp4 $ ./mp4dump devtools_fragmented.mp4 | head [ftyp] size=8+28 ... [moov] size=8+1109 ... [moof] size=8+600 ... [mdat] size=8+138679 [moof] size=8+536 ... [mdat] size=8+24490 ... ...
...ftype followed by a single moov...
You'll also notice multiple moof/mdat pairs.
When transcoding with ffmpeg, you'll want the flag: -movflags frag_keyframe+empty_moov
m = MediaSource
m.onsourceopen = () =>
s = m.addSourceBuffer 'codec'
s.onupdateend = () =>
if numChunks === totalChunks
m.endOfStream()
else
s.appendBuffer nextChunk
s.appendBuffer arrayBufferOfContent
video.src = URL.createObjectURL m
Some browsers over-aggresively cache CORS related content.
In Safari, Develop > Empty Caches or Disable Caches
In Firefox 41, enable private browsing.
bugs